package $4reflection;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class $4_3_FnGetMethod {
    static final Logger logger = LoggerFactory.getLogger($4_3_FnGetMethod.class);
    public static void main(String[] args) throws NoSuchMethodException, SecurityException, IllegalAccessException, IllegalArgumentException, InvocationTargetException {
        /**
         * 通过反射-调用方法
         * 可以通过Class实例获取所有Method信息。Class类提供了以下几个方法来获取Method：
         * Method getMethod(name, Class...)：获取某个public的Method（包括父类）
         * Method getDeclaredMethod(name, Class...)：获取当前类的某个Method（不包括父类）
         * Method[] getMethods()：获取所有public的Method（包括父类）
         * Method[] getDeclaredMethods()：获取当前类的所有Method（不包括父类）
         */
        Class stdClass = Student.class;
        // 获取public方法getScore，参数为String:
        System.out.println(stdClass.getMethod("getScore", String.class));
        // 获取继承的public方法getName，无参数:
        System.out.println(stdClass.getMethod("getName"));
        // 获取private方法getGrade，参数为int:
        System.out.println(stdClass.getDeclaredMethod("getGrade", int.class));
        
        /**
         * 一个Method对象包含一个方法的所有信息：
         * getName()：返回方法名称，例如："getScore"；
         * getReturnType()：返回方法返回值类型，也是一个Class实例，例如：String.class；
         * getParameterTypes()：返回方法的参数类型，是一个Class数组，例如：{String.class, int.class}；
         * getModifiers()：返回方法的修饰符，它是一个int，不同的bit表示不同的含义。
         */
        
        // 用反射来调用substring方法
        // 
     // String对象:
        String s = "Hello world";
        // 获取String substring(int)方法，参数为int:
        Method m = String.class.getMethod("substring", int.class);
        // 在s对象上调用该方法并获取结果:
        String r = (String) m.invoke(s, 6);
        // 打印调用结果:
        System.out.println(r);
        
        // 调用静态方法
        // 调用静态方法时，由于无需指定实例对象，所以invoke方法传入的第一个参数永远为null。
        
        // 获取Integer.parseInt(String)方法，参数为String:
        Method mst = Integer.class.getMethod("parseInt", String.class);
        // 调用该静态方法并获取结果:
        Integer n = (Integer) mst.invoke(null, "12345");
        // 打印调用结果:
        System.out.println(n);
        
        // 调用非public方法
        // 和Field类似，对于非public方法，我们虽然可以通过Class.getDeclaredMethod()获取该方法实例，但直接对其调用将得到一个IllegalAccessException。
        // 为了调用非public方法，我们通过Method.setAccessible(true)允许其调用
        Person p = new Person();
        Method mp = p.getClass().getDeclaredMethod("setName", String.class);
        mp.setAccessible(true);
        mp.invoke(p, "Bob");
        System.out.println(p.name);
        
        // 此外，setAccessible(true)可能会失败。
        // 如果JVM运行期存在SecurityManager，那么它会根据规则进行检查，有可能阻止setAccessible(true)。
        // 例如，某个SecurityManager可能不允许对java和javax开头的package的类调用setAccessible(true)，这样可以保证JVM核心库的安全。
        
        /**
         * 多态
         * 我们来考察这样一种情况：一个Person类定义了hello()方法，并且它的子类Student也覆写了hello()方法，
         * 那么，从Person.class获取的Method，作用于Student实例时，调用的方法到底是哪个？
         */ 
        // 获取Person的hello方法:
        Method h = Person.class.getMethod("hello");
        // 对Student实例调用hello方法:
        h.invoke(new Student());
        // 运行上述代码，发现打印出的是Student:hello，因此，使用反射调用方法时，仍然遵循多态原则：即总是调用实际类型的覆写方法（如果存在）。
        
        /**
         * 小结
         * Java的反射API提供的Method对象封装了方法的所有信息：
         * 通过Class实例的方法可以获取Method实例：getMethod()，getMethods()，getDeclaredMethod()，getDeclaredMethods()；
         * 通过Method实例可以获取方法信息：getName()，getReturnType()，getParameterTypes()，getModifiers()；
         * 通过Method实例可以调用某个对象的方法：Object invoke(Object instance, Object... parameters)；
         * 通过设置setAccessible(true)来访问非public方法；
         * 通过反射调用方法时，仍然遵循多态原则。
         */
    }

}
